﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using NLite.Data;

namespace NLite.Data
{
    public partial class InsertNode : TableNode
    {
        NodeCollection<NameValue> InnerNameValues;
        public IList<NameValue> NameValues { get { return InnerNameValues; } }

        public InsertNode(string tableName)
            : base(tableName, null, NodeType.Insert)
        {
            InnerNameValues = new NodeCollection<NameValue>();
        }
    }

    public partial class DeleteNode : TableNode
    {
        public WhereNode Where { get; private set; }

        public DeleteNode(string tableName)
            : base(tableName, null, NodeType.Delete)
        {
            Where = new WhereNode();
        }

    }

    public partial class UpdateNode : TableNode
    {
        NodeCollection<NameValue> InnerNameValues;
        public IList<NameValue> NameValues { get { return InnerNameValues; } }
        public WhereNode Where { get; private set; }

        public UpdateNode(string tableName)
            : base(tableName, null, NodeType.Update)
        {
            InnerNameValues = new NodeCollection<NameValue>();
            Where = new WhereNode();
        }

    }

    public partial class SelectNode : TableNode
    {
        private NodeCollection<IProjectionNode> InnerProjections;
        private NodeCollection<JoinNode> InnerJoins;
        private NodeCollection<CollectionNode> InnerCollections;

        public IList<IProjectionNode> Projections { get { return InnerProjections; } }
        public IList<JoinNode> Joins { get { return InnerJoins; } }
        public IList<CollectionNode> Collections { get { return InnerCollections; } }
        public WhereNode Where { get; private set; }
        public GroupNode Group { get; private set; }
        public HavingNode Having { get; private set; }
        public OrderNode OrderBy { get; private set; }
        public SelectIntoNode Into { get; private set; }

        public string PrimaryKeyName { get; set; }
        public bool IsDistinct { get; set; }
        public int PageSize { get; set; }
        public int PageIndex { get; set; }
        
        public SelectNode As(string alias)
        {
        	Alias = alias;
        	return this;
        }

        public SelectNode(string table) : this(table, null) { }
        public SelectNode(string table, string alias)
            : base(table, alias, NodeType.Select)
        {
            InnerProjections = new NodeCollection<IProjectionNode>();
            InnerJoins = new NodeCollection<JoinNode>();
            InnerCollections = new NodeCollection<CollectionNode>();
            Where = new WhereNode();
            Group = new GroupNode();
            Having = new HavingNode();
            OrderBy = new OrderNode();

            IsDistinct = true;
            PageSize = -1;
            PrimaryKeyName = "Id";
        }

        public void SetInto(string table)
        {
            Into = new SelectIntoNode(table);
        }


    }

    public partial class CollectionNode : Node
    {
        public readonly CollectionOperator Type;
        public SelectNode Query { get; internal set; }
        public CollectionNode(SelectNode query, CollectionOperator type):base(NodeType.Collection)
        {
            Type = type;
            Query = query;
        }
    }

    public partial class SelectIntoNode : Node
    {
        public string Table { get; private set; }
        public string AnotherDatabase { get; internal set; }
        public SelectIntoNode(string table):base(NodeType.SelectInto)
        {
            if (string.IsNullOrEmpty(table))
                throw new ArgumentNullException("table");
            Table = table;
        }

    }

    public partial class TableNode : Node
    {
        public string Name{ get; internal set;}
        public string Alias { get;protected internal set;}

        internal TableNode(string tableName, string alias,NodeType type):base(type)
        {
            this.Name = tableName;
            this.Alias = alias;
        }

        internal TableNode(string tableName, string alias) : this(tableName, alias, NodeType.Table) { }
    }

    
    public interface ITable
    {
        string TableName { get; }
        string TableAlias { get; }
    }
    

    public partial class JoinNode : TableNode
    {
        public JoinType Type { get; private set; }
        public ICriterionNode Condition { get; internal set; }

        internal JoinNode(JoinType joinType, string tableName, string alias, ICriterionNode condtion)
            : base(tableName, alias, NodeType.Join)
        {

            this.Type = joinType;
            this.Condition = condtion;
        }
        
        public JoinNode As(string alias)
        {
        	Alias = alias;
        	return this;
        }
    }

    public partial class GroupNode : Node
    {
        NodeCollection<IColumnNode> InnerItems;
        public IList<IColumnNode> Items { get { return InnerItems; } }
        public GroupNode():base(NodeType.Group)
        {
            InnerItems = new NodeCollection<IColumnNode>();
        }
    }

    public partial class OrderItemNode : Node
    {
        public IProjectionNode Column { get; internal set; }
        public OrderDirection Direction {get;private set;}

        internal OrderItemNode(IProjectionNode col, OrderDirection orderDirection)
            : base(NodeType.OrderItem)
        {
            this.Column = col;
            this.Direction = orderDirection;
        }
        
        public OrderItemNode Asc()
        {
        	Direction = OrderDirection.Asc;
        	return this;
        }
        
        public OrderItemNode Desc()
        {
        	Direction = OrderDirection.Desc;
        	return this;
        }
    }

    public partial class OrderNode : Node
    {
        NodeCollection<OrderItemNode> InnerItems;
        public IList<OrderItemNode> Items { get { return InnerItems; } }

        public OrderNode():base(NodeType.Order)
        {
            InnerItems = new NodeCollection<OrderItemNode>();
        }
    }

    public partial class ArithmeticNode : Node
    {
        public INode Left { get; internal set; }
        public INode Right { get; internal set; }
        public readonly ArithmeticOperator Operator;

        public bool? Priority { get; private set; }

        public ArithmeticNode(INode first, ArithmeticOperator op, INode second)
            : base(NodeType.Arithmetic)
        {
            Left = first;
            Operator = op;
            Right = second;

            if (first.NodeType == Data.NodeType.Arithmetic)
                MarkPriortity(first, op);
            else if (first.NodeType == Data.NodeType.SqlObject)
            {
                var o = first as SqlObject;
                if (o.Expression != null && o.Expression.NodeType == Data.NodeType.Arithmetic)
                    MarkPriortity(o.Expression, op);
            }
        }

        private void MarkPriortity(INode exp, ArithmeticOperator op)
        {
            var preExp = exp as ArithmeticNode;
            if (preExp != null && preExp.Operator < op)
            {
                if (preExp.Operator == ArithmeticOperator.Add && op != ArithmeticOperator.Subtract)
                    Priority = true;
            }
        }
    }
}
